Repository navigation
Conversation
The LED intensity sum_col_value is a plain sum over the search box and therefore contains scene-dependent background (stray light, smoke path radiance). Under smoke the LED dims while the background remains, biasing the transmission high and the reconstructed extinction coefficient low. Add a new quantity bgsub_sum_col_value computed alongside the existing sums: the local background is estimated as the median of the positive pixels in a 2 px border ring of the search area (zeros are excluded because raw Bayer arrays mask foreign-channel pixels with 0) and subtracted pixel-wise with clipping at 0 before summing. The value is written as an additional CSV column and picked up by the HDF aggregation, so it can be selected in the analysis via reference_property = bgsub_sum_col_val. Defaults are unchanged: sum_col_value is not modified and old CSV files still parse correctly.
Test with a synthetic Gaussian LED spot plus constant background that bgsub_sum_col_value is independent of the background level and matches the pure LED integral (also with a Bayer-like zero mask), while sum_col_value scales with the background. Also cover the background estimator edge cases and the HDF column name detection for old (5-column) and new (6-column) CSV files.
…default results - new suite 07: bgsub reference property reproduces ground truth on clean images; default analysis results are bit-identical with and without the bgsub column (old data format); constant background biases sum_col_val but not bgsub_sum_col_val - test library: optional constant background for generated test images, reference_property parameter for analysis config and result checks, keywords to strip the bgsub column and compare result files
|
Hi Manuel, thanks for adding the feature. For traceability can you maybe elaborate the feature and model behind that here? Thank you! |
|
Hi Kristian, sure — here is the feature and the model behind it. What the feature adds. Step 3 computes one additional quantity per LED and image, bgsub_sum_col_value: the local background Median → robust against LED tails and hot pixels; positive-only → raw Bayer arrays mask foreign-channel pixels with exactly 0, which would otherwise pull the median to ~0; clip at 0 → masked pixels stay untouched. The analysis uses it only when reference_property = bgsub_sum_col_val is set in config_analysis.ini — the default pipeline is unchanged (verified bit-identical by the new acceptance suite 07, which also covers reading pre-existing 5-column step-3 files). The model behind it. The search-area sum contains, besides the LED, an additive background (ambient/stray light plus a small estimator noise floor) that does not dim with smoke. Because this floor which propagates to the extinction coefficient as Empirical basis (Hasenheide data, camera F1L). The LED-free border ring falls only to 0.86 of its reference value while the LED signal falls to 0.51 — direct evidence of light in the box that the smoke does not dim. The floor fraction measured independently from the ring time series ( |
…files read_hdf only builds analysis/channel0/all_parameters.h5 from the step 3 files if the binary does not exist yet. In the test "Default Analysis Is Not Changed By The Bgsub Column" the binary already existed from the previous suites, so both analysis runs read the same binary and the stripped files were never used. Remove the binary before each run. The strip keyword now also shortens the column header and takes the number of columns to keep, so it can simulate files of any older format. (cherry picked from commit ed7ddc9)
The noise on the input extinction coefficients was drawn without seed, so the rmse checks against the ground truth varied between runs (bgsub on clean images 0.039-0.066 against the bound 0.06 in repeated runs). create_test_data and create_test_data_stacked now seed the generator. The default seed is 1: with seed 0 the nonlinear solver misses image 3 of the test data (rmse 0.178; linear solver on the same data 0.014), while seeds 1-7 are fine (nonlinear rmse <= 0.036). This is a weak case of the nonlinear solver, not of the test bounds. (squashed from 1df8435 and db59b0b on ManuelOsburg/LEDSmokeAnalysis)
|
Hi Kristian, two test-only commits were added, so the step 3 output and
An update on the model, from the Hasenheide 2.0 data:
In my fork I built opt-in follow-ups on top of this PR. The defaults stay bit-identical:
If you are interested, I would offer them as separate PRs once #130 and #131 are merged. Would you merge these two as they are now? Best, |
|
Hi Manuel, thanks for the detailed write-up — the bias formula, the ring-vs-LED evidence from the Hasenheide data and the synthetic-twin check make the case well, and keeping it opt-in behind Two things before this can go in, one of them a blocker. 1. Blocker:
|
| columns written | glued field | -s3 then --analysis |
|
|---|---|---|---|
master |
18 | 2325.0521e+01 (parses as a number) |
works, fit columns shifted by one |
| this PR | 19 | 4.1210e+035.0521e+01 |
ValueError |
The fix is one comma in get_data_string, and then _get_column_names has to be corrected for the fitted format as well — see below.
2. _get_column_names and the (6, 20) check
if parameters.shape[1] in (6, 20): # files written since the local background subtraction was added
columns.append("bgsub_sum_col_val")6 is right for -s3_fast. 20 never occurs: with the missing comma the fitted file has 19 columns, and once the comma is added it has 20 but the name list is still one short, because the existing list omits fit_time:
columns.extend(["x", "y", "dx", "dy", "A", "alpha", "wx", "wy", "fit_success", "fit_fun", "fit_nfev"])That missing name is what makes the final parameters.shape[1] != len(columns) - 1 guard fail, which sends the fitted case to _get_old_columns. That returns [] for anything other than 15 or 4 columns, so _get_column_names ends up returning just ['width', 'height'].
I would rather not keep extending the positional heuristic — the next column added anywhere in the row breaks it again silently. Deriving the names from the exact column count reads better and fails loudly:
base_columns = ["img_id", "led_id", "led_array_id", "sum_col_val", "mean_col_val", "max_col_val"]
bgsub_columns = ["bgsub_sum_col_val"]
led_center_columns = ["led_center_x", "led_center_y"]
fit_columns = ["x", "y", "dx", "dy", "A", "alpha", "wx", "wy", "fit_success", "fit_fun", "fit_nfev", "fit_time"]and then match parameters.shape[1] + 1 against the known combinations, falling back to _get_old_columns only for the genuinely old layouts and raising if nothing matches. Note 'img_id' is not in the file but prepended on load, which is where the - 1 in the current guard comes from; that off-by-one is easy to get wrong and worth a comment.
Your TestColumnNames covers the 6- and 5-column cases, which is why this stayed hidden — a fitted-format case there would have caught it. Could you add one?
I tried both changes together on your head: the fitted file then has 20 columns, -s3 followed by --analysis goes through, and your suites stay green (12/12 acceptance, 11/11 unit). Happy to send the patch if that saves you the round trip.
Thanks again — the feature itself I am happy with.
…column names from the column count LEDAnalysisData.__str__ concatenated the main and the fit part without a delimiter, so the last main field and led_center_x were glued into one field. Before the bgsub column this was max_col_value (an int) and the glued field still parsed as a number, with all following columns shifted by one. With bgsub_sum_col_value last, two floats in scientific notation are glued and np.loadtxt fails, so -s3 followed by --analysis crashed. Add the comma. _get_column_names extended the names by position and missed fit_time, so the check against the column count sent fitted files to _get_old_columns, which returned no names. The names are now looked up by the exact number of columns (img_id is not in the file but prepended on load, hence + 1): the -s3_fast layouts with and without bgsub and the fitted layout, all names including fit_time. Fitted files written before this fix (18 columns) are still read with the previous names and a warning that max_col_val and the fit columns are not valid; sum_col_val and mean_col_val are. The oldest layouts (4 and 15 columns) are handled as before, any other column count raises a ValueError. Unit tests cover a fitted row written by LEDAnalysisData, the legacy fitted layout, the oldest layouts and unknown column counts. Reported by Kristian Börger in FireDynamics#131.
Step 3 with fits (-s3) followed by --analysis was not covered by the acceptance tests, the corresponding S3 test is commented out. The new test in suite 07 runs -s3 on the test data and checks the extinction coefficients with sum_col_val and bgsub_sum_col_val against the ground truth. It fails on 630fcec (the binary cannot be built) and passes with the delimiter fix; -s3 takes about 2 minutes on the test data.
|
Hi Kristian, thanks for the careful review and for tracking this down. You are right on both points. I reproduced the glued field on
One decision I'd like your view on: fitted files written by the current This also fixes the column shift of the fit columns that you wanted to open a separate issue for, so that issue may not be needed. If you open it anyway, I'll link it. Unit tests: 18/18. Robot suites 02–07 pass, including the new test. S1 fails on Windows independently, see #135. Best, |
|
Hi Manuel, thanks for the quick turnaround — On your question: keep the warning, please don't raise. In the same spirit, one addition: files written by this branch before the delimiter fix (19 columns, master has moved since you branched#137, #140 and #141 landed in the meantime, so please merge
One small note on the new test: on Once the merge is resolved and CI is green I am happy to approve. The separate issue for the fit column shift I will not open — Best, |
…ature/local-background-subtraction Conflict in LedsaATestLibrary.py: keep master's NOISE_SEED with np.random.default_rng and drop the seed parameter of this branch; keep the background argument of create_test_data, create_test_image and create_img_array and the reference_property argument of load_extinction_coefficients_computed; keep both the keywords of this branch and the new ones of master. The strip keyword now names the encoding of its text I/O, as required by the encoding check of the CI. The background subtraction suite becomes 08_test_background_subtraction, since master now has 07_test_corrupted_images.
…tests Required by the encoding check of the CI (master since FireDynamics#139): two write_text calls in test_background_subtraction.py did not name one.
…imiter Fitted files written by this branch before the delimiter fix (19 columns, bgsub_sum_col_value glued to led_center_x) cannot be loaded at all: np.loadtxt fails before any column name is derived, and with silent=True only the bare ValueError was shown. _get_column_names now re-raises it with the file name and the hint to run step 3 again, like the warning for the 18-column fitted files of master. Suggested by Kristian Börger in FireDynamics#131.
The new suite 07 (corrupted images) runs before it and empties analysis/channel0 in its teardown, so the first tests of the background subtraction suite found no step 3 files. A suite setup now runs -s3_fast in the work directory and removes a binary of an earlier run. With master's NOISE_SEED the rmse checks of the suite keep their bounds with margin: bgsub on clean images 0.045 / 0.034 / 0.029 (< 0.06), with background sum 0.13 / 0.18 / 0.13 (> 0.05) and bgsub 0.014 / 0.016 / 0.022 (< 0.05), fitted output sum 0.014-0.022 (< 0.05) and bgsub 0.029-0.045 (< 0.06).
|
Hi Kristian, thanks, and for the clear guidance on the merge. All points are addressed:
Checks before pushing:
One observation outside this PR, from checking the interplay with #137: Best, |
No description provided.